{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'1.3.0'"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import torch\n",
    "import numpy as np\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "from PIL import Image\n",
    "from torchvision import transforms\n",
    "from torchvision import models,datasets\n",
    "torch.__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4.2.2 使用Tensorboard在 PyTorch 中进行可视化 \n",
    "\n",
    "##  Tensorboard 简介\n",
    "Tensorboard是tensorflow内置的一个可视化工具，它通过将tensorflow程序输出的日志文件的信息可视化使得tensorflow程序的理解、调试和优化更加简单高效。\n",
    "Tensorboard的可视化依赖于tensorflow程序运行输出的日志文件，因而tensorboard和tensorflow程序在不同的进程中运行。\n",
    "TensorBoard给我们提供了极其方便而强大的可视化环境。它可以帮助我们理解整个神经网络的学习过程、数据的分布、性能瓶颈等等。\n",
    "\n",
    "tensorboard虽然是tensorflow内置的可视化工具，但是他们跑在不同的进程中，所以Github上已经有大神将tensorboard应用到Pytorch中 [链接在这里]( https://github.com/lanpa/tensorboardX)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  Tensorboard 安装\n",
    "首先需要安装tensorboard\n",
    "\n",
    "`pip install tensorboard`\n",
    "\n",
    "\n",
    "\n",
    "~~ 然后再安装tensorboardx ~~\n",
    "\n",
    "~~ `pip install tensorboardx` ~~\n",
    "pytorch 1.1以后的版本内置了SummaryWriter 函数,所以不需要再安装tensorboardx了\n",
    "\n",
    "安装完成后与 visdom一样执行独立的命令\n",
    "`tensorboard --logdir logs` 即可启动，默认的端口是 6006,在浏览器中打开 `http://localhost:6006/` 即可看到web页面。\n",
    "\n",
    "这里要说明的是 微软的Edge浏览器css会无法加载，使用chrome正常显示"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  页面\n",
    "与visdom不同，tensorboard针对不同的类型人为的区分多个标签，每一个标签页面代表不同的类型。\n",
    "下面我们根据不同的页面功能做个简单的介绍，更多详细内容请参考官网\n",
    "### SCALAR\n",
    "对标量数据进行汇总和记录，通常用来可视化训练过程中随着迭代次数准确率(val acc)、损失值(train/test loss)、学习率(learning rate)、每一层的权重和偏置的统计量(mean、std、max/min)等的变化曲线\n",
    "### IMAGES\n",
    "可视化当前轮训练使用的训练/测试图片或者 feature maps\n",
    "### GRAPHS\n",
    "可视化计算图的结构及计算图上的信息，通常用来展示网络的结构\n",
    "### HISTOGRAMS\n",
    "可视化张量的取值分布，记录变量的直方图(统计张量随着迭代轮数的变化情况）\n",
    "###  PROJECTOR\n",
    "全称Embedding Projector 高维向量进行可视化"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  使用\n",
    "在使用前请先去确认执行`tensorboard --logdir logs` 并保证 `http://localhost:6006/` 页面能够正常打开\n",
    "\n",
    "### 图像展示\n",
    "首先介绍比较简单的功能，查看我们训练集和数据集中的图像，这里我们使用现成的图像作为展示。这里使用wikipedia上的一张猫的图片[这里](https://en.wikipedia.org/wiki/Cat#/media/File:Felis_silvestris_catus_lying_on_rice_straw.jpg)\n",
    "\n",
    "引入 tensorboardX 包"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 这里的引用也要修改成torch的引用\n",
    "#from tensorboardX import SummaryWriter\n",
    "from torch.utils.tensorboard import SummaryWriter"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1280, 853)"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cat_img = Image.open('./1280px-Felis_silvestris_catus_lying_on_rice_straw.jpg')\n",
    "cat_img.size"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这是一张1280x853的图，我们先把她变成224x224的图片，因为后面要使用的是vgg16"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [],
   "source": [
    "transform_224 = transforms.Compose([\n",
    "        transforms.Resize(224), # 这里要说明下 Scale 已经过期了，使用Resize\n",
    "        transforms.CenterCrop(224),\n",
    "        transforms.ToTensor(),\n",
    "    ])\n",
    "cat_img_224=transform_224(cat_img)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "将图片展示在tebsorboard中："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [],
   "source": [
    "writer = SummaryWriter(log_dir='./logs', comment='cat image') # 这里的logs要与--logdir的参数一样\n",
    "writer.add_image(\"cat\",cat_img_224)\n",
    "writer.close()# 执行close立即刷新，否则将每120秒自动刷新"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "浏览器访问 `http://localhost:6006/#images` 即可看到猫的图片 \n",
    "### 更新损失函数\n",
    "更新损失函数和训练批次我们与visdom一样使用模拟展示，这里用到的是tensorboard的SCALAR页面"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = torch.FloatTensor([100])\n",
    "y = torch.FloatTensor([500])\n",
    "\n",
    "for epoch in range(30):\n",
    "    x = x * 1.2\n",
    "    y = y / 1.1\n",
    "    loss = np.random.random()\n",
    "    with SummaryWriter(log_dir='./logs', comment='train') as writer: #可以直接使用python的with语法，自动调用close方法\n",
    "        writer.add_histogram('his/x', x, epoch)\n",
    "        writer.add_histogram('his/y', y, epoch)\n",
    "        writer.add_scalar('data/x', x, epoch)\n",
    "        writer.add_scalar('data/y', y, epoch)\n",
    "        writer.add_scalar('data/loss', loss, epoch)\n",
    "        writer.add_scalars('data/data_group', {'x': x,\n",
    "                                                'y': y}, epoch)\n",
    "\n",
    " "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "浏览器访问 `http://localhost:6006/#scalars` 即可看到图形\n",
    "### 使用PROJECTOR对高维向量可视化\n",
    "PROJECTOR的的原理是通过PCA，T-SNE等方法将高维向量投影到三维坐标系（降维度）。Embedding Projector从模型运行过程中保存的checkpoint文件中读取数据，默认使用主成分分析法（PCA）将高维数据投影到3D空间中，也可以通过设置设置选择T-SNE投影方法，这里做一个简单的展示。\n",
    "\n",
    "我们还是用第三章的mnist代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "BATCH_SIZE=512 \n",
    "EPOCHS=20 \n",
    "train_loader = torch.utils.data.DataLoader(\n",
    "        datasets.MNIST('data', train=True, download=True, \n",
    "                       transform=transforms.Compose([\n",
    "                           transforms.ToTensor(),\n",
    "                           transforms.Normalize((0.1307,), (0.3081,))\n",
    "                       ])),\n",
    "        batch_size=BATCH_SIZE, shuffle=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [],
   "source": [
    "class ConvNet(nn.Module):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        # 1,28x28\n",
    "        self.conv1=nn.Conv2d(1,10,5) # 10, 24x24\n",
    "        self.conv2=nn.Conv2d(10,20,3) # 128, 10x10\n",
    "        self.fc1 = nn.Linear(20*10*10,500)\n",
    "        self.fc2 = nn.Linear(500,10)\n",
    "    def forward(self,x):\n",
    "        in_size = x.size(0)\n",
    "        out = self.conv1(x) #24\n",
    "        out = F.relu(out)\n",
    "        out = F.max_pool2d(out, 2, 2)  #12\n",
    "        out = self.conv2(out) #10\n",
    "        out = F.relu(out)\n",
    "        out = out.view(in_size,-1)\n",
    "        out = self.fc1(out)\n",
    "        out = F.relu(out)\n",
    "        out = self.fc2(out)\n",
    "        out = F.log_softmax(out,dim=1)\n",
    "        return out\n",
    "model = ConvNet()\n",
    "optimizer = torch.optim.Adam(model.parameters())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train(model, train_loader, optimizer, epoch):\n",
    "    n_iter=0\n",
    "    model.train()\n",
    "    for batch_idx, (data, target) in enumerate(train_loader):\n",
    "        optimizer.zero_grad()\n",
    "        output = model(data)\n",
    "        loss = F.nll_loss(output, target)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        if(batch_idx+1)%30 == 0: \n",
    "            n_iter=n_iter+1\n",
    "            print('Train Epoch: {} [{}/{} ({:.0f}%)]\\tLoss: {:.6f}'.format(\n",
    "                epoch, batch_idx * len(data), len(train_loader.dataset),\n",
    "                100. * batch_idx / len(train_loader), loss.item()))\n",
    "            #相对于以前的训练方法 主要增加了以下内容\n",
    "            out = torch.cat((output.data, torch.ones(len(output), 1)), 1) # 因为是投影到3D的空间，所以我们只需要3个维度\n",
    "            with SummaryWriter(log_dir='./logs', comment='mnist') as writer: \n",
    "                #使用add_embedding方法进行可视化展示\n",
    "                writer.add_embedding(\n",
    "                    out,\n",
    "                    metadata=target.data,\n",
    "                    label_img=data.data,\n",
    "                    global_step=n_iter)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里节省时间，只训练一次"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train Epoch: 0 [14848/60000 (25%)]\tLoss: 0.352312\n",
      "Train Epoch: 0 [30208/60000 (50%)]\tLoss: 0.202950\n",
      "Train Epoch: 0 [45568/60000 (75%)]\tLoss: 0.156494\n"
     ]
    }
   ],
   "source": [
    "train(model, train_loader, optimizer, 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "打开 `http://localhost:6006/#projector` 即可看到效果。\n",
    "\n",
    "目前测试投影这部分也是有问题的，根据官网文档的代码进行测试，也显示不出来，正在找原因\n",
    "\n",
    "### 绘制网络结构\n",
    "在pytorch中我们可以使用print直接打印出网络的结构，但是这种方法可视化效果不好，这里使用tensorboard的GRAPHS来实现网络结构的可视化。\n",
    "由于pytorch使用的是动态图计算，所以我们这里要手动进行一次前向的传播.\n",
    "\n",
    "使用Pytorch已经构建好的模型进行展示"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "VGG(\n",
      "  (features): Sequential(\n",
      "    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (1): ReLU(inplace=True)\n",
      "    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (3): ReLU(inplace=True)\n",
      "    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
      "    (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (6): ReLU(inplace=True)\n",
      "    (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (8): ReLU(inplace=True)\n",
      "    (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
      "    (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (11): ReLU(inplace=True)\n",
      "    (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (13): ReLU(inplace=True)\n",
      "    (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (15): ReLU(inplace=True)\n",
      "    (16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
      "    (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (18): ReLU(inplace=True)\n",
      "    (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (20): ReLU(inplace=True)\n",
      "    (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (22): ReLU(inplace=True)\n",
      "    (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
      "    (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (25): ReLU(inplace=True)\n",
      "    (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (27): ReLU(inplace=True)\n",
      "    (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (29): ReLU(inplace=True)\n",
      "    (30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
      "  )\n",
      "  (avgpool): AdaptiveAvgPool2d(output_size=(7, 7))\n",
      "  (classifier): Sequential(\n",
      "    (0): Linear(in_features=25088, out_features=4096, bias=True)\n",
      "    (1): ReLU(inplace=True)\n",
      "    (2): Dropout(p=0.5, inplace=False)\n",
      "    (3): Linear(in_features=4096, out_features=4096, bias=True)\n",
      "    (4): ReLU(inplace=True)\n",
      "    (5): Dropout(p=0.5, inplace=False)\n",
      "    (6): Linear(in_features=4096, out_features=1000, bias=True)\n",
      "  )\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "vgg16 = models.vgg16(pretrained=True) # 这里下载预训练好的模型\n",
    "print(vgg16) # 打印一下这个模型"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在前向传播前，先要把图片做一些调整"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [],
   "source": [
    "transform_2 = transforms.Compose([\n",
    "    transforms.Resize(224), \n",
    "    transforms.CenterCrop((224,224)),\n",
    "    transforms.ToTensor(),\n",
    "    transforms.Normalize(mean=[0.485, 0.456, 0.406],\n",
    "                                std=[0.229, 0.224, 0.225])\n",
    "])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用上一张猫的图片进行前向传播"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([1, 3, 224, 224])"
      ]
     },
     "execution_count": 59,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vgg16_input=transform_2(cat_img)[np.newaxis]# 因为pytorch的是分批次进行的，所以我们这里建立一个批次为1的数据集\n",
    "vgg16_input.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "开始前向传播，打印输出值"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "287"
      ]
     },
     "execution_count": 60,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "out = vgg16(vgg16_input)\n",
    "_, preds = torch.max(out.data, 1)\n",
    "label=preds.numpy()[0]\n",
    "label"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "将结构图在tensorboard进行展示"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [],
   "source": [
    "with SummaryWriter(log_dir='./logs', comment='vgg161') as writer:\n",
    "    writer.add_graph(vgg16, vgg16_input)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对于Pytorch的1.3版本来说，实测 SummaryWriter在处理结构图的时候是有问题的（或者是需要加什么参数，目前我还没找到），所以建议大家继续使用tensorboardx。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "deep learning",
   "language": "python",
   "name": "dl"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
